package com.ccx.demo.config;

import com.alibaba.fastjson.JSON;
import com.google.common.io.CharStreams;
import com.support.mvc.encrypt.AesApi;
import com.support.mvc.entity.base.Result;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

/**
 * api 接口数据加解密
 *
 * @author 谢长春 2022-02-08
 */
@Configuration(proxyBeanMethods = false)
@Slf4j
public class ApiEncryptConfiguration {

    /**
     * 响应结果加密
     */
    @Component
    @ConditionalOnProperty(value = "spring.app.encrypt.api.enabled", havingValue = "true")
    @ControllerAdvice(basePackages = "com.ccx.demo")
    public static class AesApiEncryptResponseBodyAop implements ResponseBodyAdvice<Object> {

        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            // 是否执行 beforeBodyWrite
            return true;
        }

        @Override
        public Object beforeBodyWrite(Object body,
                                      MethodParameter returnType,
                                      MediaType selectedContentType,
                                      Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                      ServerHttpRequest request,
                                      ServerHttpResponse response) {
            if (body instanceof Result) {
                return AesApi.encrypt(JSON.toJSONString(body));
            }
            return body;
        }
    }

    /**
     * 请求参数解密， 只有 POST PUT DELETE PATCH 请求，且 body 不为空的情况才需要解密。 上传文件也不会走解密逻辑
     */
    @Component
    @ConditionalOnProperty(value = "spring.app.encrypt.api.enabled", havingValue = "true")
    @ControllerAdvice(basePackages = "com.ccx.demo")
    public static class AesApiDecryptRequestBodyAop implements RequestBodyAdvice {

        @Override
        public boolean supports(MethodParameter methodParameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
            // 是否执行 beforeBodyRead 、 afterBodyRead 、 handleEmptyBody
            return true;
        }

        @Override
        public HttpInputMessage beforeBodyRead(HttpInputMessage request,
                                               MethodParameter parameter,
                                               Type targetType,
                                               Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
            // 请求数据
            @Cleanup final InputStreamReader inputStreamReader = new InputStreamReader(request.getBody());
            final String body = CharStreams.toString(inputStreamReader);
            if (log.isDebugEnabled()) {
                log.debug("密文:{}", body);
            }
            return new HttpInputMessage() {
                @SneakyThrows
                @Override
                public InputStream getBody() {
                    @Cleanup final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
                            Optional.of(body)
                                    .map(AesApi::decrypt)
                                    .map(val -> val.getBytes(StandardCharsets.UTF_8))
                                    .orElse(new byte[0])
                    );
                    return byteArrayInputStream;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return request.getHeaders();
                }
            };
        }

        @Override
        public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                    Class<? extends HttpMessageConverter<?>> converterType) {
            log.info("afterBodyRead--------------------------------");
            return body;
        }

        @Override
        public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                      Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
            log.info("handleEmptyBody--------------------------------");
            return body;
        }

    }
}
